#pragma once

#include <memory>
#include <vector>
using std::make_shared;
using std::shared_ptr;
using std::vector;

#include "../umlmodel_global.h"
#include <QtCore>

//#include "./UmlFactory.h"

namespace uml {

/// <summary>
/// JsonSerializable
/// Operations for json serialization
/// </summary>
class UMLMODEL_EXPORT JsonSerializable {
public:
    virtual void fromJson(const QJsonObject& json, QStringList& errorList) {}
    virtual void toJson(QJsonObject& json) {}
    virtual bool writeToFile(const QString& fileName) {
        QFile jsonFile(fileName);

        if (!jsonFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            return false;
        }

        QJsonObject json;
        toJson(json);

        QJsonDocument jdoc(json);
        QTextStream out(&jsonFile);
        out.setCodec("utf-8");
        out << jdoc.toJson(QJsonDocument::Indented);
        return true;
    }
    virtual bool readFromFile(const QString& fileName) {
        QFile jsonFile(fileName);
        if (!jsonFile.open(QIODevice::ReadOnly)) {
            return false;
        }

        QJsonParseError errorMessage;
        auto jdoc = QJsonDocument::fromJson(jsonFile.readAll(), &errorMessage);

        if (errorMessage.error != QJsonParseError::NoError) {
            return false;
        }

        if (!jdoc.isObject()) {
            return false;
        }

        QStringList errorList;
        fromJson(jdoc.object(), errorList);

        if (!errorList.isEmpty()) {
            return false;
        }
        return true;
    }

    template <class T>
    static shared_ptr<T> create(
        const QJsonValue& jsonValue,
        QStringList& errorList
    ) {
        if (jsonValue.isUndefined() || jsonValue.isNull()) {
            return nullptr;
        }

        std::shared_ptr<T> item;
        auto json = jsonValue.toObject();
        if (json.isEmpty()) {
            item = make_shared<T>();
            return item;
        }

        QString type = json["_umlType"].toString();
        if (type.isEmpty()) {
            item = make_shared<T>();
            item->fromJson(json, errorList);
            return item;
        }
        auto createFunc = typeMap_.value(type, nullptr);
        if (createFunc) {
            item = dynamic_pointer_cast<T>(createFunc());
            item->fromJson(json, errorList);
        }
        return item;
    }

    template <class T>
    static inline void fromJsonArray(
        vector<shared_ptr<T>>& items,
        const QString& prop,
        const QJsonObject& json,
        QStringList& errorList
    ) {
        items.clear();
        QJsonArray jsonItems;
        jsonItems = json.value(prop).toArray();
        for (int i = 0; i < jsonItems.size(); ++i) {
            shared_ptr<T> item = create<T>(jsonItems[i], errorList);
            items.push_back(item);
        }
    }

    template <class T>
    static inline void toJsonArray(
        vector<shared_ptr<T>>& items,
        const QString& prop,
        QJsonObject& json
    ) {
        if (items.size() > 0) {
            QJsonArray jsonItems = QJsonArray();
            for (int i = 0; i < items.size(); ++i) {
                QJsonObject item;
                items.at(i)->toJson(item);
                jsonItems.append(item);
            }
            json.insert(prop, jsonItems);
        }
    }

    static inline void fromJsonArray(
        vector<QString>& items,
        const QString& prop,
        const QJsonObject& json,
        QStringList& errorList
    ) {
        items.clear();
        QJsonArray jsonItems;
        jsonItems = json.value(prop).toArray();
        for (int i = 0; i < jsonItems.size(); ++i) {
            items.push_back(jsonItems[i].toString());
        }
    }

    static inline void toJsonArray(
        vector<QString>& items,
        const QString& prop,
        QJsonObject& json
    ) {
        if (items.size() > 0) {
            QJsonArray jsonItems = QJsonArray();
            for (int i = 0; i < items.size(); ++i) {
                jsonItems.append(items.at(i));
            }
            json.insert(prop, jsonItems);
        }
    }

    static inline void fromJsonArray(
        vector<bool>& items,
        const QString& prop,
        const QJsonObject& json,
        QStringList& errorList
    ) {
        items.clear();
        QJsonArray jsonItems;
        jsonItems = json.value(prop).toArray();
        for (int i = 0; i < jsonItems.size(); ++i) {
            items.push_back(jsonItems[i].toBool());
        }
    }

    static inline void toJsonArray(
        vector<bool>& items,
        const QString& prop,
        QJsonObject& json
    ) {
        if (items.size() > 0) {
            QJsonArray jsonItems = QJsonArray();
            for (int i = 0; i < items.size(); ++i) {
                jsonItems.append(QJsonValue(items.at(i)));
            }
            json.insert(prop, jsonItems);
        }
    }

    static inline void fromJsonArray(
        vector<QPointF>& items,
        const QString& prop,
        const QJsonObject& json,
        QStringList& errorList
    ) {
        items.clear();
        QJsonArray jsonItems;
        jsonItems = json.value(prop).toArray();
        for (int i = 0; i < jsonItems.size(); ++i) {
            QPointF item;
            item.setX(jsonItems[i].toObject().value("x").toDouble());
            item.setY(jsonItems[i].toObject().value("y").toDouble());
            items.push_back(item);
        }
    }

    static inline void toJsonArray(
        vector<QPointF>& items,
        const QString& prop,
        QJsonObject& json
    ) {
        if (items.size() > 0) {
            QJsonArray jsonItems = QJsonArray();
            for (int i = 0; i < items.size(); ++i) {
                QJsonObject item;
                item.insert("x", items.at(i).x());
                item.insert("y", items.at(i).y());
                jsonItems.append(item);
            }
            json.insert(prop, jsonItems);
        }
    }

    static inline void fromJsonArray(
        vector<QRectF>& items,
        const QString& prop,
        const QJsonObject& json,
        QStringList& errorList
    ) {
        items.clear();
        QJsonArray jsonItems;
        jsonItems = json.value(prop).toArray();
        for (int i = 0; i < jsonItems.size(); ++i) {
            QRectF item;
            item.setX(jsonItems[i].toObject().value("x").toDouble());
            item.setY(jsonItems[i].toObject().value("y").toDouble());
            item.setWidth(jsonItems[i].toObject().value("width").toDouble());
            item.setHeight(jsonItems[i].toObject().value("height").toDouble());
            items.push_back(item);
        }
    }

    static inline void toJsonArray(
        vector<QRectF>& items,
        const QString& prop,
        QJsonObject& json
    ) {
        if (items.size() > 0) {
            QJsonArray jsonItems = QJsonArray();
            for (int i = 0; i < items.size(); ++i) {
                QJsonObject item;
                item.insert("x", items.at(i).x());
                item.insert("y", items.at(i).y());
                item.insert("width", items.at(i).width());
                item.insert("height", items.at(i).height());
                jsonItems.append(item);
            }
            json.insert(prop, jsonItems);
        }
    }

    static void registerType(
        const QString key,
        std::function<shared_ptr<JsonSerializable>()> func
    );

protected: // Fields
    static QMap<QString, std::function<shared_ptr<JsonSerializable>()>>
        typeMap_;
};
} // namespace uml
